/*
 * Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "se_napi_oma_session.h"

#include <memory>
#include "se_napi_oma_common.h"
#include "se_napi_oma_reader.h"
#include "se_napi_oma_channel.h"

namespace OHOS {
namespace SE {
namespace KITS {

thread_local napi_ref NapiSEOmaSession::consRef_ = nullptr;

napi_value JSOmaSessionClassConstructor(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
    NapiSEOmaSession* omaJSClass = new NapiSEOmaSession();

    napi_wrap(
        env, thisVar, omaJSClass,
        [](napi_env env, void* data, void* hint) {
            NapiSEOmaSession* service = (NapiSEOmaSession*)data;
            if (service) {
                delete service;
                service = nullptr;
            }
        },
        nullptr,
        nullptr);
    return thisVar;
}

void NapiSEOmaSession::DefineSEOmaSessionJsClass(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_FUNCTION("getReader", NapiSEOmaSession::GetReader),
        DECLARE_NAPI_FUNCTION("getATR", NapiSEOmaSession::GetATR),
        DECLARE_NAPI_FUNCTION("close", NapiSEOmaSession::Close),
        DECLARE_NAPI_FUNCTION("isClosed", NapiSEOmaSession::IsClosed),
        DECLARE_NAPI_FUNCTION("closeChannels", NapiSEOmaSession::CloseChannels),
        DECLARE_NAPI_FUNCTION("openBasicChannel", NapiSEOmaSession::OpenBasicChannel),
        DECLARE_NAPI_FUNCTION("openLogicalChannel", NapiSEOmaSession::OpenLogicalChannel),
    };
    // define JS class SEOmaSession, JS_Constructor is the callback function
    napi_value constructor = nullptr;
    napi_define_class(env, "SEOmaSession", NAPI_AUTO_LENGTH, JSOmaSessionClassConstructor,
        nullptr, sizeof(desc)/sizeof(desc[0]), desc, &constructor);
    napi_create_reference(env, constructor, 1, &consRef_);
}

napi_value NapiSEOmaSession::GetReader(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaSession::GetReader:");
    napi_value result = nullptr;
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_0;
    napi_value argv[] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NapiSEOmaCommon::CheckArgCountAndThrow(env, argc, ARGV_NUM_0);
    // TO-DO:get readers by calling native method
    int32_t status = SEErrorCode::ERR_NONE;
    if (!NapiSEOmaCommon::CheckSEStatusCodeAndThrow(env, status, "getReader")) {
        return NapiSEOmaCommon::CreateUndefined(env);
    }

    // new instance of JS object
    napi_value constructor = nullptr;
    NAPI_CALL(env, napi_get_reference_value(env, NapiSEOmaReader::consRef_, &constructor));
    NAPI_CALL(env, napi_new_instance(env, constructor, argc, argv, &result));
    return result;
}

napi_value NapiSEOmaSession::GetATR(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaSession::GetATR:");
    napi_value result = nullptr;
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_0;
    napi_value argv[] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NapiSEOmaCommon::CheckArgCountAndThrow(env, argc, ARGV_NUM_0);

    // TO-DO:get ATRs by call native method, throw exception when failed
    std::vector<int32_t> aidVec = {1, 2};
    int32_t status = SEErrorCode::ERR_NONE;
    if (!NapiSEOmaCommon::CheckSEStatusCodeAndThrow(env, status, "getATR")) {
        return NapiSEOmaCommon::CreateUndefined(env);
    }
    // new instance of number array
    NapiSEOmaCommon::Int32VectorToJS(env, aidVec, result);
    return result;
}

napi_value NapiSEOmaSession::Close(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaSession::Close:");
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_0;
    napi_value argv[] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NapiSEOmaCommon::CheckArgCountAndThrow(env, argc, ARGV_NUM_0);

    // TO-DO:close session by call native method, throw exception when failed
    int32_t status = 0;
    if (!NapiSEOmaCommon::CheckSEStatusCodeAndThrow(env, status, "close")) {
        // output error log
    }
    return NapiSEOmaCommon::CreateUndefined(env);
}

napi_value NapiSEOmaSession::IsClosed(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaSession::IsClosed:");
    napi_value result = nullptr;
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_0;
    napi_value argv[] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NapiSEOmaCommon::CheckArgCountAndThrow(env, argc, ARGV_NUM_0);
    int32_t status = SEErrorCode::ERR_NONE;
    // TO-DO:retrieve isclose by call native method, throw exception when failed

    if (!NapiSEOmaCommon::CheckSEStatusCodeAndThrow(env, status, "isClosed")) {
        return NapiSEOmaCommon::CreateUndefined(env);
    }
    napi_get_boolean(env, (status == SEErrorCode::ERR_NONE), &result);
    return result;
}

napi_value NapiSEOmaSession::CloseChannels(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaSession::CloseChannels:");
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_0;
    napi_value argv[] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NapiSEOmaCommon::CheckArgCountAndThrow(env, argc, ARGV_NUM_0);
    int32_t status = 0;
    // TO-DO: close channels by call native method, throw exception when fail

    if (!NapiSEOmaCommon::CheckSEStatusCodeAndThrow(env, status, "closeChannels")) {
        //TO-DO: output error log
    }
    return NapiSEOmaCommon::CreateUndefined(env);
}

napi_value NapiSEOmaSession::OpenBasicChannel(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaSession::OpenBasicChannel:");
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_3;
    napi_value argv[ARGV_NUM_3] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NAPI_ASSERT(env, argc > ARGV_NUM_0, "parameter number error!");
    NAPI_ASSERT(env, argc <= ARGV_NUM_3, "parameter number error!");
    if (argc >= ARGV_NUM_2) {
        bool isTrue = NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_1], napi_number) ||
        NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_1], napi_function);
        NAPI_ASSERT(env, isTrue, "parameter type error!");
    }

    std::unique_ptr<SessionContext> asyncContext = std::make_unique<SessionContext>(env);
    if (!NapiSEOmaCommon::ParseBytesVector(env, asyncContext->aid, argv[ARGV_INDEX_0])) {
        ErrorLog("parse int vector failed");
        return NapiSEOmaCommon::CreateUndefined(env);
    }

    if (argc == ARGV_NUM_2) {
        if (NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_1], napi_number)) {
            napi_get_value_int32(env, argv[ARGV_INDEX_1], &asyncContext->p2);
            DebugLog("arg number is 2, argv: %{public}d", asyncContext->p2);
        } else {
            // callback function
            napi_create_reference(env, argv[ARGV_INDEX_1], 1, &(asyncContext->callbackRef));
            DebugLog("arg number is 2, argv 2 callback");
        }
    } else if (argc == ARGV_NUM_3) {
            NAPI_ASSERT(env, NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_1], napi_number),
                "parameter type error!");
            NAPI_ASSERT(env, NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_2], napi_function),
                "parameter type error!");
            napi_get_value_int32(env, argv[ARGV_INDEX_1], &asyncContext->p2);
            napi_create_reference(env, argv[ARGV_INDEX_2], 1, &(asyncContext->callbackRef));
            DebugLog("NapiSEOmaSession::OpenBasicChannel argv number 3");
    }

    // TO-DO:open basic channel by call native method, throw exception when failed
    int32_t status = SEErrorCode::ERR_NONE;

    if (!NapiSEOmaCommon::CheckSEStatusCodeAndThrow(env, status, "openBasicChannel")) {
        //TO-DO:output error log
        return NapiSEOmaCommon::CreateUndefined(env);
    }
    return HandleAsyncWork(env, asyncContext.release(), "OpenBasicChannel",
        NativeOpenBasicChannel, NativeOpenBasicChannelCallBack);
}

napi_value NapiSEOmaSession::OpenLogicalChannel(napi_env env, napi_callback_info info)
{
    DebugLog("Enter function NapiSEOmaSession::OpenLogicalChannel:");
    napi_value thisVar = nullptr;
    size_t argc = ARGV_NUM_3;
    napi_value argv[ARGV_NUM_3] = {nullptr};
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
    NAPI_ASSERT(env, argc > ARGV_NUM_0, "parameter number error!");
    NAPI_ASSERT(env, argc <= ARGV_NUM_3, "parameter number error!");

    if (argc >= ARGV_NUM_2) {
        bool isTrue = NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_1], napi_number) ||
        NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_1], napi_function);
        NAPI_ASSERT(env, isTrue, "parameter type error!");
        return NapiSEOmaCommon::CreateUndefined(env);
    }

    std::unique_ptr<SessionContext> asyncContext = std::make_unique<SessionContext>(env);
    if (!NapiSEOmaCommon::ParseBytesVector(env, asyncContext->aid, argv[ARGV_INDEX_0])) {
        ErrorLog("parse int vector failed");
        return NapiSEOmaCommon::CreateUndefined(env);
    }

    if (argc == ARGV_NUM_2) {
        if (NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_1], napi_number)) {
            napi_get_value_int32(env, argv[ARGV_INDEX_1], &asyncContext->p2);
            DebugLog("argv: %{public}d", asyncContext->p2);
        } else {
            // callback function
            napi_create_reference(env, argv[ARGV_INDEX_1], 1, &(asyncContext->callbackRef));
            DebugLog("argv callback");
        }
    } else if (argc == ARGV_NUM_3) {
            NAPI_ASSERT(env, NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_1], napi_number),
                "parameter type error!");
            NAPI_ASSERT(env, NapiSEOmaCommon::IsTargetValueType(env, argv[ARGV_INDEX_2], napi_function),
                "parameter type error!");
            napi_get_value_int32(env, argv[ARGV_INDEX_1], &asyncContext->p2);
            napi_create_reference(env, argv[ARGV_INDEX_2], 1, &(asyncContext->callbackRef));
            DebugLog("NapiSEOmaSession::OpenLogicalChannel argv number 3");
    }

    int32_t status = SEErrorCode::ERR_NONE;
    // TO-DO:open logic channel by call native method, throw exception when failed

    if (!NapiSEOmaCommon::CheckSEStatusCodeAndThrow(env, status, "openLogicalChannel")) {
        return NapiSEOmaCommon::CreateUndefined(env);
    }
    return HandleAsyncWork(env, asyncContext.release(), "openLogicalChannel",
        NativeOpenLogicChannel, NativeOpenLogicChannelCallBack);
}

napi_value NapiSEOmaSession::HandleAsyncWork(napi_env env, AsyncContext *context, std::string workName,
    napi_async_execute_callback execute, napi_async_complete_callback complete)
{
    DebugLog("HandleAsyncWork start workName = %{public}s", workName.c_str());
    napi_value result = nullptr;
    if (context->callbackRef == nullptr) {
        napi_create_promise(env, &context->deferred, &result);
    } else {
        napi_get_undefined(env, &result);
    }
    napi_value resource = NapiSEOmaCommon::CreateUndefined(env);
    napi_value resourceName = nullptr;
    napi_create_string_utf8(env, workName.data(), NAPI_AUTO_LENGTH, &resourceName);
    napi_create_async_work(env, resource, resourceName, execute, complete, (void *)context, &context->work);
    napi_queue_async_work(env, context->work);
    return result;
}

void NapiSEOmaSession::NativeOpenBasicChannel(napi_env env, void *data)
{
    DebugLog("enter function NapiSEOmaSession::NativeOpenBasicChannel");
    if (data == nullptr) {
        ErrorLog("NapiSEOmaSession::NativeOpenBasicChannel data is nullptr");
        return NapiSEOmaCommon::ThrowParameterError(env);
    }
    //auto asyncContext = (AsyncContext *)data;
    // TO-DO: call native method
}

void NapiSEOmaSession::NativeOpenBasicChannelCallBack(napi_env env, napi_status status, void *data)
{
    DebugLog("enter function NapiSEOmaSession::NativeOpenBasicChannelCallBack");
    if (data == nullptr) {
        ErrorLog("NapiSEOmaSession::NativeOpenBasicChannelCallBack data is nullptr");
        return NapiSEOmaCommon::ThrowParameterError(env);
    }
    auto asyncContext = (AsyncContext *)data;
    if (asyncContext->deferred != nullptr) {
        // TO-DO: call native method
        DebugLog("asyncContext->deferred is not null");
        napi_value promiseValue = nullptr;
        napi_get_reference_value(env, NapiSEOmaChannel::constRef_, &promiseValue);
        napi_resolve_deferred(env, asyncContext->deferred, promiseValue);
    } else if (asyncContext->callbackRef != nullptr) {
        DebugLog("asyncContext callback ...");
        napi_value callback = nullptr;
        napi_get_reference_value(env, asyncContext->callbackRef, &callback);
        // TO-DO: call native method
        napi_delete_reference(env, asyncContext->callbackRef);
    }
    napi_delete_async_work(env, asyncContext->work);
    delete asyncContext;
    asyncContext = nullptr;
}

void NapiSEOmaSession::NativeOpenLogicChannel(napi_env env, void *data)
{
    DebugLog("enter function NapiSEOmaSession::NativeOpenLogicChannel");
    if (data == nullptr) {
        ErrorLog("NapiSEOmaSession::NativeOpenLogicChannel data is nullptr");
        return NapiSEOmaCommon::ThrowParameterError(env);
    }
    //auto asyncContext = (AsyncContext *)data;
    // TO-DO: call native method
}

void NapiSEOmaSession::NativeOpenLogicChannelCallBack(napi_env env, napi_status status, void *data)
{
    DebugLog("enter function NapiSEOmaSession::NativeOpenLogicChannelCallBack");
    if (data == nullptr) {
        ErrorLog("NapiSEOmaSession::NativeOpenLogicChannelCallBack data is nullptr");
        return NapiSEOmaCommon::ThrowParameterError(env);
    }
    auto asyncContext = (AsyncContext *)data;
    if (asyncContext->deferred != nullptr) {
        // TO-DO:
    } else if (asyncContext->callbackRef != nullptr) {
        napi_value callback = nullptr;
        // TO-DO: call native method
        napi_get_reference_value(env, asyncContext->callbackRef, &callback);
        napi_delete_reference(env, asyncContext->callbackRef);
    }
    napi_delete_async_work(env, asyncContext->work);
    delete asyncContext;
    asyncContext = nullptr;
}
} // namespace KITS
} // namespace SE
} // namespace OHOS
